package com.gitee.zjbqdzq;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import com.beust.jcommander.JCommander;
import com.gitee.zjbqdzq.entity.Album;
import com.gitee.zjbqdzq.entity.Track;
import com.gitee.zjbqdzq.entity.TrackInfoResult;
import com.gitee.zjbqdzq.entity.Tracks;
import com.gitee.zjbqdzq.util.HibernateUtil;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.CookieManager;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;

public class Main2 {
    private static final Log log = Log.get();
    private static final String baseURL = "https://www.ximalaya.com";
    private static final HttpClient httpClient;

    static {
        try {
            httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).version(HttpClient.Version.HTTP_1_1).sslContext(SSLContext.getDefault()).cookieHandler(new CookieManager()).build();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private static final byte[] r = new byte[]{(byte) 188, (byte) 174, (byte) 178, (byte) 234, (byte) 171, (byte) 147, 70, 82, 76, 72, (byte) 192, (byte) 132, 60, 17, 30, 127, (byte) 184, (byte) 233, 48, 105, 38, (byte) 232, (byte) 240, 21, 47, (byte) 252, 41, (byte) 229, (byte) 209, (byte) 213, 71, 40, 63, (byte) 152, (byte) 156, 88, 51, (byte) 141, (byte) 139, (byte) 145, (byte) 133, 2, (byte) 160, (byte) 191, 11, 100, 10, 78, (byte) 253, (byte) 151, 42, (byte) 166, 92, 22, (byte) 185, (byte) 140, (byte) 164, 91, (byte) 194, (byte) 175, (byte) 239, (byte) 217, (byte) 177, 75, 19, (byte) 225, 94, 107, 125, (byte) 138, (byte) 242, 31, (byte) 182, (byte) 150, 15, 24, (byte) 226, 29, 80, 116, (byte) 168, 118, 28, 1, (byte) 186, (byte) 220, (byte) 158, 79, 59, (byte) 244, 119, 9, (byte) 189, (byte) 161, 74, (byte) 130, (byte) 221, 56, (byte) 216, (byte) 241, (byte) 212, 26, (byte) 218, (byte) 170, 85, (byte) 165, (byte) 153, 69, (byte) 238, 93, (byte) 255, (byte) 142, 3, (byte) 159, (byte) 215, 67, 33, (byte) 249, 53, (byte) 176, 77, (byte) 254, (byte) 222, 25, 115, 101, (byte) 148, 16, 13, (byte) 237, (byte) 197, 5, 58, (byte) 157, (byte) 135, (byte) 248, (byte) 223, 61, (byte) 198, (byte) 211, 110, 44, 54, 111, 52, (byte) 227, 4, 46, (byte) 205, 7, (byte) 219, (byte) 136, 14, 87, 114, 64, 104, 50, 39, (byte) 203, 81, (byte) 196, 43, (byte) 163, (byte) 173, 109, 108, (byte) 187, 102, (byte) 195, 37, (byte) 235, 65, (byte) 190, 113, (byte) 149, (byte) 143, 8, 27, (byte) 155, (byte) 207, (byte) 134, 123, (byte) 224, (byte) 129, (byte) 245, 62, 66, (byte) 172, 122, 126, 12, (byte) 162, (byte) 214, 90, (byte) 247, (byte) 251, 124, (byte) 201, (byte) 236, 117, (byte) 183, 73, 95, 89, (byte) 246, (byte) 181, (byte) 179, 83, (byte) 228, (byte) 193, 99, 6, 45, 112, 32, (byte) 154, (byte) 128, (byte) 230, (byte) 131, (byte) 206, (byte) 243, 57, 84, (byte) 146, 0, 35, 96, (byte) 250, (byte) 137, 36, (byte) 208, 103, 34, 68, (byte) 204, (byte) 231, (byte) 144, 120, 98, (byte) 202, 49, (byte) 210, 23, (byte) 200, 18, 86, 55, 121, 20, (byte) 199, 97, (byte) 167, (byte) 180, (byte) 169, 106};
    private static final byte[] n = new byte[]{20, (byte) 234, (byte) 159, (byte) 167, (byte) 230, (byte) 233, 58, (byte) 255, (byte) 158, 36, (byte) 210, (byte) 254, (byte) 133, (byte) 166, 59, 63, (byte) 209, (byte) 177, (byte) 184, (byte) 155, 85, (byte) 235, 94, 1, (byte) 242, 87, (byte) 228, (byte) 232, (byte) 191, 3, 69, (byte) 178};
    private static final byte[] o = new byte[]{(byte) 183, (byte) 174, 108, 16, (byte) 131, (byte) 159, (byte) 250, 5, (byte) 239, 110, (byte) 193, (byte) 202, (byte) 153, (byte) 137, (byte) 251, (byte) 176, 119, (byte) 150, 47, (byte) 204, 97, (byte) 237, 1, 71, (byte) 177, 42, 88, (byte) 218, (byte) 166, 82, 87, 94, 14, (byte) 195, 69, 127, (byte) 215, (byte) 240, (byte) 225, (byte) 197, (byte) 238, (byte) 142, 123, 44, (byte) 219, 50, (byte) 190, 29, (byte) 181, (byte) 186, (byte) 169, 98, (byte) 139, (byte) 185, (byte) 152, 13, (byte) 141, 76, 6, (byte) 157, (byte) 200, (byte) 132, (byte) 182, 49, 20, 116, (byte) 136, 43, (byte) 155, (byte) 194, 101, (byte) 231, (byte) 162, (byte) 242, (byte) 151, (byte) 213, 53, 60, 26, (byte) 134, (byte) 211, 56, 28, (byte) 223, 107, (byte) 161, (byte) 199, 15, (byte) 229, 61, 96, 41, 66, (byte) 158, (byte) 254, 21, (byte) 165, (byte) 253, 103, 89, 3, (byte) 168, 40, (byte) 246, 81, 95, 58, 31, (byte) 172, 78, 99, 45, (byte) 148, (byte) 187, (byte) 222, 124, 55, (byte) 203, (byte) 235, 64, 68, (byte) 149, (byte) 180, 35, 113, (byte) 207, 118, 111, 91, 38, (byte) 247, (byte) 214, 7, (byte) 212, (byte) 209, (byte) 189, (byte) 241, 18, 115, (byte) 173, 25, (byte) 236, 121, (byte) 249, 75, 57, (byte) 216, 10, (byte) 175, 112, (byte) 234, (byte) 164, 70, (byte) 206, (byte) 198, (byte) 255, (byte) 140, (byte) 230, 12, 32, 83, 46, (byte) 245, 0, 62, (byte) 227, 72, (byte) 191, (byte) 156, (byte) 138, (byte) 248, 114, (byte) 220, 90, 84, (byte) 170, (byte) 128, 19, 24, 122, (byte) 146, 80, 39, 37, 8, 34, 22, 11, 93, (byte) 130, 63, (byte) 154, (byte) 244, (byte) 160, (byte) 144, 79, 23, (byte) 133, 92, 54, 102, (byte) 210, 65, 67, 27, (byte) 196, (byte) 201, 106, (byte) 143, 52, 74, 100, (byte) 217, (byte) 179, 48, (byte) 233, 126, 117, (byte) 184, (byte) 226, 85, (byte) 171, (byte) 167, 86, 2, (byte) 147, 17, (byte) 135, (byte) 228, (byte) 252, 105, 30, (byte) 192, (byte) 129, (byte) 178, 120, 36, (byte) 145, 51, (byte) 163, 77, (byte) 205, 73, 4, (byte) 188, 125, (byte) 232, 33, (byte) 243, 109, (byte) 224, 104, (byte) 208, (byte) 221, 59, 9};
    private static final byte[] a = new byte[]{(byte) 204, 53, (byte) 135, (byte) 197, 39, 73, 58, (byte) 160, 79, 24, 12, 83, (byte) 180, (byte) 250, 101, 60, (byte) 206, 30, 10, (byte) 227, 36, 95, (byte) 161, 16, (byte) 135, (byte) 150, (byte) 235, 116, (byte) 242, 116, (byte) 165, (byte) 171};
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10, task -> new Thread(task, "executors-"));

    static Tracks getPlayList(int pageNum, int pageSize, XimalayaCommander ximalayaCommander) {
        HttpRequest request = HttpRequest
                .newBuilder()
                .GET()
                .uri(URI.create(StrUtil.format("{}/revision/album/v1/getTracksList?albumId={}&pageNum={}&pageSize={}", baseURL, ximalayaCommander.getAlbumId(), pageNum, pageSize)))
                .header("Accept", "application/json")
                .header("Cookie", ximalayaCommander.getCookies())
                .build();
        HttpResponse<String> response;
        try {
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
        if (HttpStatus.HTTP_OK != response.statusCode()) {
            throw new RuntimeException(response.body());
        }
        String body = response.body();
        Tracks tracks = JSONUtil.toBean(body, Tracks.class);
        if (HttpStatus.HTTP_OK == tracks.getRet()) {

            return tracks;
        }
        return null;
    }

    static TrackInfoResult getTrackInfo(XimalayaCommander ximalayaCommander, Long trackId) {
        String str = "https://www.ximalaya.com/mobile-playpage/track/v3/baseInfo/{}?device={}&trackId={}&trackQualityLevel=1";
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(StrUtil.format(str, System.currentTimeMillis(), "www2", trackId))).header("Cookie", ximalayaCommander.getCookies()).header("Referer", "https://www.ximalaya.com/album/" + ximalayaCommander.getAlbumId()).header("Accept", "*/*").header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36").header("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\"").header("sec-ch-ua-mobile", "?0").header("sec-ch-ua-platform", "\"Windows\"").header("sec-fetch-dest", "empty").header("sec-fetch-mode", "cors").header("sec-fetch-site", "same-origin").header("accept-encoding", "gzip, deflate, br, zstd").header("accept-language", "zh-CN,zh;q=0.9").header("cache-control", "no-cache").GET().build();
        HttpResponse<InputStream> response;
        try {
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("获取播放列表：{}", response.statusCode());
        if (HttpStatus.HTTP_OK != response.statusCode()) {
            return null;
        }
        if ("gzip".equals(response.headers().firstValue("content-encoding").orElse(""))) {
            try (GZIPInputStream gzipInputStream = new GZIPInputStream(response.body())) {
                String body = IoUtil.readUtf8(gzipInputStream);
                TrackInfoResult trackInfoResult = JSONUtil.toBean(body, TrackInfoResult.class);
                log.info("获取播放列表：{}", trackInfoResult);
                if (0 == trackInfoResult.getRet()) {
                    return trackInfoResult;
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
                return null;
            }
        } else {
            try (InputStream inputStream = response.body()) {
                String body = IoUtil.readUtf8(inputStream);
                TrackInfoResult trackInfoResult = JSONUtil.toBean(body, TrackInfoResult.class);
                log.info("获取播放列表：{}", trackInfoResult);
                if (0 == trackInfoResult.getRet()) {
                    return trackInfoResult;
                } else {
                    return null;
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
                return null;
            }
        }
    }

    public static String parseUrl(String link) {

        try {
            String replacedLink = link.replace("_", "/").replace("-", "+");
            byte[] decodedBytes = Base64.getDecoder().decode(replacedLink);
            if (decodedBytes == null || decodedBytes.length < 16) {
                return link;
            }

            byte[] rBytes = new byte[decodedBytes.length - 16];
            System.arraycopy(decodedBytes, 0, rBytes, 0, decodedBytes.length - 16);
            byte[] nBytes = new byte[16];
            System.arraycopy(decodedBytes, decodedBytes.length - 16, nBytes, 0, 16);

            for (int i = 0; i < rBytes.length; i++) {
                rBytes[i] = o[rBytes[i] & 0xFF];
            }

            for (int i = 0; i < rBytes.length; i += 16) {
                xor(rBytes, i, nBytes);
            }

            for (int i = 0; i < rBytes.length; i += 32) {
                xor(rBytes, i, a);
            }

            return new String(rBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            System.out.println("Secret failed: " + e);
            return "";
        }
    }

    public static void xor(byte[] data, int offset, byte[] key) {
        int n = Math.min(data.length - offset, key.length);
        for (int i = 0; i < n; i++) {
            data[offset + i] ^= key[i];
        }
    }

    public static void main(String[] args) throws InterruptedException {
        XimalayaCommander ximalayaCommander = new XimalayaCommander();
        JCommander build = JCommander.newBuilder().addObject(ximalayaCommander).build();
        build.parse(args);
        if (ximalayaCommander.isHelp()) {
            build.usage();
            return;
        }

        serial(ximalayaCommander);

        download(ximalayaCommander);

        HibernateUtil.shutdown();
    }

    private static void download(XimalayaCommander ximalayaCommander) {
        Album album = HibernateUtil.getOne(session -> session.get(Album.class, ximalayaCommander.getAlbumId()));
        prepareDownload(ximalayaCommander, album);
        List<Track> trackList = album.getTrackList();
        trackList.parallelStream().filter(v -> !v.getDownloaded()).forEach(v -> download(ximalayaCommander, album, v));
    }

    private static void download(XimalayaCommander ximalayaCommander, Album album, Track track) {
        String saveFolder = ximalayaCommander.getSaveFolder();
        String name = album.getName();
        TrackInfoResult trackInfo = getTrackInfo(ximalayaCommander, track.getId());
        if (Objects.isNull(trackInfo)) {
            return;
        }
        TrackInfoResult.TrackInfo.PlayUrlList playUrlList = trackInfo.getTrackInfo().getPlayUrlList().getFirst();
        String url = Main2.parseUrl(playUrlList.getUrl());
        String type = playUrlList.getType().split("_")[0];
        Path path = Paths.get(saveFolder, name, track.getTitle() + "." + type);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).header("Cookie", ximalayaCommander.getCookies()).GET().build();
        try {
            HttpResponse<Path> response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(path));
            int code = response.statusCode();
            log.info("download response code {}", code);
            if (code == 200) {
                track.setDownloaded(true);
                HibernateUtil.execute(session -> session.merge(track));
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private static void prepareDownload(XimalayaCommander ximalayaCommander, Album album) {
        String name = album.getName();
        String saveFolder = ximalayaCommander.getSaveFolder();
        Path path = Paths.get(saveFolder, name);
        try {
            Files.createDirectories(path);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean allLoaded(XimalayaCommander ximalayaCommander) {
        String albumId = ximalayaCommander.getAlbumId();
        Long one = HibernateUtil.getOne(session -> session.createNativeQuery(String.format("""
                select count(0) from ALBUM_TRACK at
                         left join TRACK t on at.TRACKLIST_ID = t.ID
                         where at.ALBUM_ID = %s and at.TRACKLIST_ID not in (select distinct TRACK_ID from TRACK_PLAYURLLIST);
                """, albumId), Long.class).uniqueResult());

        return one.equals(0L);
    }

    private static void serial(XimalayaCommander ximalayaCommander) {
        Album album = HibernateUtil.getOne(session -> session.get(Album.class, ximalayaCommander.getAlbumId()));
        if (Objects.isNull(album)) {
            HibernateUtil.execute(session -> {
                Album newAlbum = init(ximalayaCommander);
                session.persist(newAlbum);
            });
        }
    }

    private static Album init(XimalayaCommander ximalayaCommander) {
        Tracks tracks = getPlayList(1, 1, ximalayaCommander);
        Album album = Album.build(Objects.requireNonNull(tracks));
        List<Tracks.Data.Track> tracks1 = getPlayUrlList(ximalayaCommander);
        List<Track> list = tracks1.stream().map(Track::build).toList();
        album.setTrackList(list);
        return album;
    }

    private static Thread getThread() {
        Thread watch = new Thread(() -> {
            ThreadPoolExecutor executorService1 = (ThreadPoolExecutor) executorService;
            try {
                while (true) {
                    TimeUnit.MILLISECONDS.sleep(500L);
                    System.out.printf("总任务数：%d, 正在下载的任务数：%d\r", executorService1.getQueue().size(), executorService1.getActiveCount());
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        watch.setDaemon(true);
        return watch;
    }

    private static void download(XimalayaCommander ximalayaCommander, Tracks.Data.Track track) {
        String albumTitle = track.getAlbumTitle();
        String title = track.getTitle();
        TrackInfoResult trackInfo = getTrackInfo(ximalayaCommander, Long.valueOf(track.getTrackId()));
        TrackInfoResult.TrackInfo trackInfo1 = Objects.requireNonNull(trackInfo).getTrackInfo();
        TrackInfoResult.TrackInfo.PlayUrlList first = trackInfo1.getPlayUrlList().getFirst();
        String ext = getFileExt(first);
        Path path = Paths.get(ximalayaCommander.getSaveFolder(), albumTitle, String.join(".", title, ext));
        String url = first.getUrl();
        url = parseUrl(url);
        try {
            Files.createDirectories(path.getParent());
            HttpRequest request = HttpRequest.newBuilder(URI.create(url)).header("Cookie", ximalayaCommander.getCookies()).GET().build();
            httpClient.send(request, HttpResponse.BodyHandlers.ofFile(path));
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private static String getFileExt(TrackInfoResult.TrackInfo.PlayUrlList first) {
        return first.getType().split("_")[0].toLowerCase();
    }

    private static List<Tracks.Data.Track> getPlayUrlList(XimalayaCommander ximalayaCommander) {
        Integer total = getTotal(ximalayaCommander);
        int max = total / 100 + (total % 100 == 0 ? 0 : 1);
        List<Tracks> tracksFlux = getTracksFlux(ximalayaCommander, max);
        return getTrackInfoFlux(ximalayaCommander, tracksFlux);
    }

    private static List<Tracks.Data.Track> getTrackInfoFlux(XimalayaCommander ximalayaCommander, List<Tracks> tracksFlux) {
        return tracksFlux.stream().peek(v -> log.info(v.toString())).flatMap(data -> data.getData().getTracks().stream().filter(Objects::nonNull)).toList();
    }

    private static Integer getTotal(XimalayaCommander ximalayaCommander) {
        Tracks join = getPlayList(1, 1, ximalayaCommander);
        return join.getData().getTrackTotalCount();
    }

    private static List<Tracks> getTracksFlux(XimalayaCommander ximalayaCommander, int max) {
        return Stream.iterate(1, i -> i + 1).limit(max).map(v -> getPlayList(v, 100, ximalayaCommander)).filter(Objects::nonNull).toList();
    }
}
